// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

/*
 * Method.cpp
 *
 *  Created on: Nov 24, 2009
 *      Author: dhovemey
 */

#include <cstring>
#include <cassert>
#include "util/DataInputStream.h"
#include "util/DataOutputStream.h"
#include "vm/Magic.h"
#include "vm/ClassFormatException.h"
#include "vm/Method.h"

const int DEFAULT_CAPACITY = 16;

const uint32_t MAX_CODE_SIZE = 65536;

Method::Method()
	: m_code(new uint8_t[DEFAULT_CAPACITY])
	, m_numBytes(0)
	, m_codeCapacity(DEFAULT_CAPACITY)
	, m_flags(0)
	, m_numLocals(0)
{
	addParam(L"self"); // in all methods, param 0 is the self reference
}

Method::Method(const std::wstring &name)
	: m_name(name)
	, m_code(new uint8_t[DEFAULT_CAPACITY])
	, m_numBytes(0)
	, m_codeCapacity(DEFAULT_CAPACITY)
	, m_flags(0)
	, m_numLocals(0)
{
	addParam(L"self"); // in all methods, param 0 is the self reference
}

Method::~Method()
{
	delete[] m_code;
}

const std::wstring &Method::getName() const
{
	return m_name;
}

int Method::getNumBytes() const
{
	return m_numBytes;
}

const uint8_t *Method::getCode() const
{
	return m_code;
}

void Method::addParam(const std::wstring &varName)
{
	m_paramMap.push_back(varName);
}

const std::wstring &Method::getParam(int index) const
{
	return m_paramMap.at(index);
}

void Method::freeze()
{
	reallocate(m_numBytes);
}

void Method::append(uint8_t b)
{
	grow(m_numBytes + 1);
	m_code[m_numBytes++] = b;
}

void Method::setByte(uint32_t i, uint8_t b)
{
	assert(i < m_numBytes);
	m_code[i] = b;
}

void Method::read(DataInputStream *in)
{
	m_name = in->readString();

	m_flags = in->readInt();

	uint8_t numParams = (uint8_t) in->readByte();
	if (numParams >= METHOD_MAX_LOCALS) {
		throw ClassFormatException(L"Too many parameters");
	}
	for (uint8_t i = 0; i < numParams; i++) {
		std::wstring param = in->readString();
		addParam(param);
	}

	m_numLocals = (uint8_t) in->readByte();
	if (m_numLocals >= METHOD_MAX_LOCALS) {
		throw ClassFormatException(L"Too many locals");
	}
	if (m_numLocals == 0) {
		// must have at least the implicit receiver reference
		throw ClassFormatException(L"Method has 0 locals");
	}

	m_numBytes = in->readInt();
	if (m_numBytes > MAX_CODE_SIZE) {
		throw ClassFormatException(L"Maximum code size exceeded");
	}
	reallocate(m_numBytes);
	in->read((void *) m_code, m_numBytes);
}

void Method::write(DataOutputStream *out) const
{
	out->writeString(m_name);

	out->writeInt(m_flags);

	out->writeByte(m_paramMap.size() - 1);

	ParamMap::const_iterator i = m_paramMap.begin();
	i++; // skip special "self" parameter
	while (i != m_paramMap.end()) {
		out->writeString(*i);
		i++;
	}

	assert(m_numLocals > 0); // at the very least, the receiver reference is a local
	assert(m_numLocals >= getNumParams());
	out->writeByte(uint8_t(m_numLocals));

	out->writeInt(m_numBytes);
	out->write((const void *) m_code, m_numBytes);
}

void Method::grow(uint32_t size)
{
	if (m_codeCapacity >= size) {
		return;
	}
	size = std::max(size, m_codeCapacity * 2);
	reallocate(size);
}

void Method::reallocate(uint32_t size)
{
	uint8_t *codeCopy = new uint8_t[size];
	::memcpy(codeCopy, m_code, size);
	delete[] m_code;
	m_code = codeCopy;
}
